﻿; About:
;     此脚本源自ahk社区的ahk_vl版本，时过境迁，版本更迭
;     我将此脚本升级到v2版本，对部分内容做简化，并增加了更多功能。
; 说明：
;     源脚本是104键，修改后为87键。
; 添加：
;     shift+lButton 拖动位置
;     Shift+whellup\whellDown 改变透明度

; modify @dkwd

; origin:
; On-Screen ANSI Keyboard version 3 (original discussion at https://redd.it/kxh9rb)

; This customizable, non-interactive keyboard only flashes what you type. It may be used to:
;     Memorize the ANSI keyboard format without looking down
;     Record your keystrokes (like in a game or something)
;     Check key functionality on a damaged keyboard
;     Revel in or lament over your typing speed

; /u/KeronCyst added all buttons to the right of Backspace, added a Ctrl-` visibility toggle, cleaned up
; code descriptions, and made the keyboard click-through/non-interactive by default.
; /u/anonymous1184 used invisible characters (to differentiate Numpad numbers from their counterparts) and
; labels (to eliminate version 2's lists of manual character entries).
; Both redditors made aesthetic improvements (symbols + name changes) and fixed various flashing problems.

; Version 2 added color-flashing via progress bars by Lehnemann:
; https://autohotkey.com/board/topic/94703-another-approach-to-a-virtual-keyboard/

; Version 1 by Jon: http://www.autohotkey.com

; Press Ctrl-` or double-click (or right-click) the tray icon to toggle visibility of the keyboard.

; Run a search for and delete the following to make the keyboard:
;  • Clickable (Escape will then close the keyboard if it's focused on):
;        +E0x20
;  • Draggable:
;        -Caption

; Run a search for "Transparency" and edit the number to make it more/less opaque.
; Run a search for "BackgroundRed" and edit "Red" to change the flash color.
; Usable colors: https://www.autohotkey.com/docs/commands/Progress.htm#colors


#Requires AutoHotkey v2.0
#SingleInstance Force

CoordMode "Mouse", "Screen"

; class-desc: Do all the configuration here
class Config {
  ; Whether keys can be sent through the mouse
  ; If set true, gui transparency cannot be set
  ; But it doesn't do much right now,
  ; because it loses focus so that can't be sent to the right window
  static EnableInput := false

  ; Determines the gui window size by calculating the font size
  static fontSize := 14
  static fontName := 'Verdana'

  ; Default 200
  static guiCapacity := Config.EnableInput ? '' : 200
  ; Gui background color
  ; Don't use a deep color because the button can't set background color
  static guiBackColor := 'cfff2e6'
  ; The color when the button is pressed
  static guiProgressColor := 'cA1BE7B'
  ; Same as guiBackColor
  static guiProgressBackColor := SubStr(Config.guiBackColor, 2)
  static guiOption := '+AlwaysOnTop -MaximizeBox +ToolWindow -Caption +E0x20'

  ; The text on tray
  static trayItemOn := 'Show keyboard'
  static trayItemOff := 'Hide keyboard'
}

; tray setting
tray := A_TrayMenu
; set dobule-clicl
tray.ClickCount := 2
; delete standard item
tray.delete
tray.add Config.trayItemOff, K_Toggle
; division
tray.add
; add exit item
tray.add "Exit", (*) => ExitApp()
tray.default := Config.trayItemOff

; param:
; ret:   -->
; desc:  Toggle tray item name
K_Toggle(*) {
  static flag := IsSet(flag) || true
  if flag {
    tray.rename(Config.trayItemOff, Config.trayItemOn)
    VK.Cancel()
  } else {
    tray.rename(Config.trayItemOn, Config.trayItemOff)
    VK.Show()
  }
  flag := !flag
}

; Calculate object dimensions based on chosen font size:
k_KeyWidth := Config.fontSize * 3
k_KeyHeight := Config.fontSize * 3

; Spacing to be used between the keys.
k_KeyMargin := Config.fontSize // 6

; The total width of the keyboard in terms of the keys and their margins.
width := 15 * k_KeyWidth + 14 * k_KeyMargin
; Values for specially sized keys. The first and last keys of each row are critical for proper sizing.
k_KeyWidthHalf := k_KeyWidth / 2
k_TabW := Config.fontSize * 4
k_CapsW := k_KeyWidth + k_KeyMargin + k_KeyWidthHalf
k_ShiftW := 2 * k_KeyWidth + k_KeyMargin
k_SpacebarWidth := Config.fontSize * 17
k_LastKeyWidth := width - (k_TabW + 12 * k_KeyWidth + 13 * k_KeyMargin)
k_EnterWidth := width - (k_CapsW + 11 * k_KeyWidth + 12 * k_KeyMargin)
k_LastShiftWidth := width - (k_ShiftW + 10 * k_KeyWidth + 11 * k_KeyMargin)
k_LastCtrlWidth := width - (6 * k_TabW + k_SpacebarWidth + 7 * k_KeyMargin)

; Only a facilitator for creating GUI.
k_KeySize := ' w' k_KeyWidth ' h' k_KeyHeight
k_Position := ' x+' k_KeyMargin k_KeySize

; This table is used to relate the hotkeys pressed with their progress bars to flash them when pressed.
; **All uppercase**
k_Characters := Map(
  "``", 1
  , '1', 2
  , '2', 3
  , '3', 4
  , '4', 5
  , '5', 6
  , '6', 7
  , '7', 8
  , '8', 9
  , '9', 10
  , '0', 11
  , "-", 12
  , "=", 13
  , "BACKSPACE", 14
  , "TAB", 15
  , "Q", 16
  , "W", 17
  , "E", 18
  , "R", 19
  , "T", 20
  , "Y", 21
  , "U", 22
  , "I", 23
  , "O", 24
  , "P", 25
  , "[", 26
  , "]", 27
  , "\", 28
  , "CAPSLOCK", 29
  , "A", 30
  , "S", 31
  , "D", 32
  , "F", 33
  , "G", 34
  , "H", 35
  , "J", 36
  , "K", 37
  , "L", 38
  , ";", 39
  , "'", 40
  , "ENTER", 41
  , "LSHIFT", 42
  , "Z", 43
  , "X", 44
  , "C", 45
  , "V", 46
  , "B", 47
  , "N", 48
  , "M", 49
  , ",", 50
  , ".", 51
  , "/", 52
  , "RSHIFT", 53
  , "LCTRL", 54
  , "LWIN", 55
  , "LALT", 56
  , "SPACE", 57
  , "RALT", 58
  , "RWN", 59
  , "APPSKEY", 60
  , "RCTRL", 61
  , "INSERT", 62
  , "HOME", 63
  , "PGUP", 64
  , "DELETE", 65
  , "END", 66
  , "PGDN", 67
  , "UP", 68
  , "LEFT", 69
  , "DOWN", 70
  , "RIGHT", 71)

; Zero-width non-breaking space
zwnbs := Chr(8204)
labels := Map(
  "AppsKey", "App"
  , "BackSpace", Chr(0x1F844)
  , "CapsLock", "Caps"
  , "Delete", "Del"
  , "End", Chr(0x21F2)
  , "Home", Chr(0x21F1)
  , "Insert", "Ins"
  , "LAlt", "Alt"
  , "LCtrl", "Ctrl"
  , "LShift", "Shift"
  , "LWin", "Win"
  , "PgDn", "PD"
  , "PgUp", "PU"
  , "RAlt", "Alt" zwnbs
  , "RCtrl", "Ctrl" zwnbs
  , "RShift", "Shift" zwnbs
  , "RWin", "Win" zwnbs
  , "Tab", "Tab" ;Chr(0x2B7E)
  , "Left", "◀"
  , "Right", "▶"
  , "Down", "▼"
  , "Up", "▲")

class VK extends Gui {

  ; store current gui instance hwnd
  static insHwnd := unset

  __New() {
    super.__New(Config.guiOption)
    this.SetFont('s' Config.fontSize, Config.fontName)
    this.BackColor := Config.guiBackColor
    ; 1
    this.AddProgress('Section xm ym Disabled vprg1 ' k_Position)
    this.AddProgress('Disabled vprg2 ' k_Position)
    this.AddProgress('Disabled vprg3 ' k_Position)
    this.AddProgress('Disabled vprg4 ' k_Position)
    this.AddProgress('Disabled vprg5 ' k_Position)
    this.AddProgress('Disabled vprg6 ' k_Position)
    this.AddProgress('Disabled vprg7 ' k_Position)
    this.AddProgress('Disabled vprg8 ' k_Position)
    this.AddProgress('Disabled vprg9 ' k_Position)
    this.AddProgress('Disabled vprg10 ' k_Position)
    this.AddProgress('Disabled vprg11 ' k_Position)
    this.AddProgress('Disabled vprg12 ' k_Position)
    this.AddProgress('Disabled vprg13 ' k_Position)

    this.AddProgress('Disabled vprg14 x+' k_KeyMargin ' w' k_ShiftW ' h' k_KeyHeight)
    this.AddProgress('Disabled ' k_Position)
    this.AddProgress('Disabled vprg62 ' k_Position)
    this.AddProgress('Disabled vprg63 ' k_Position)
    this.AddProgress('Disabled vprg64 ' k_Position)

    ; 2
    this.AddProgress('Disabled vprg15 xs y+' k_KeyMargin ' w' k_TabW ' h' k_KeyHeight)
    this.AddProgress('Disabled vprg16 ' k_Position)
    this.AddProgress('Disabled vprg17 ' k_Position)
    this.AddProgress('Disabled vprg18 ' k_Position)
    this.AddProgress('Disabled vprg19 ' k_Position)
    this.AddProgress('Disabled vprg20 ' k_Position)
    this.AddProgress('Disabled vprg21 ' k_Position)
    this.AddProgress('Disabled vprg22 ' k_Position)
    this.AddProgress('Disabled vprg23 ' k_Position)
    this.AddProgress('Disabled vprg24 ' k_Position)
    this.AddProgress('Disabled vprg25 ' k_Position)
    this.AddProgress('Disabled vprg26 ' k_Position)
    this.AddProgress('Disabled vprg27 ' k_Position)


    this.AddProgress('Disabled vprg28 x+' k_KeyMargin ' w' k_LastKeyWidth ' h' k_KeyHeight)
    this.AddProgress('Disabled ' k_Position)
    this.AddProgress('Disabled vprg65 ' k_Position)
    this.AddProgress('Disabled vprg66 ' k_Position)
    this.AddProgress('Disabled vprg67 ' k_Position)

    ; 3
    this.AddProgress('Disabled vprg29 xs y+' k_KeyMargin ' w' k_CapsW ' h' k_KeyHeight)
    this.AddProgress('Disabled vprg30 ' k_Position)
    this.AddProgress('Disabled vprg31 ' k_Position)
    this.AddProgress('Disabled vprg32 ' k_Position)
    this.AddProgress('Disabled vprg33 ' k_Position)
    this.AddProgress('Disabled vprg34 ' k_Position)
    this.AddProgress('Disabled vprg35 ' k_Position)
    this.AddProgress('Disabled vprg36 ' k_Position)
    this.AddProgress('Disabled vprg37 ' k_Position)
    this.AddProgress('Disabled vprg38 ' k_Position)
    this.AddProgress('Disabled vprg39 ' k_Position)
    this.AddProgress('Disabled vprg40 ' k_Position)

    this.AddProgress('Disabled vprg41 x+' k_KeyMargin ' w' k_EnterWidth ' h' k_KeyHeight)
    this.AddProgress('Disabled ' k_Position)
    this.AddProgress('Disabled ' k_Position)
    this.AddProgress('Disabled ' k_Position)
    this.AddProgress('Disabled ' k_Position)

    ; 4
    this.AddProgress('Disabled vprg42 xs y+' k_KeyMargin ' w' k_ShiftW ' h' k_KeyHeight)
    this.AddProgress('Disabled vprg43 ' k_Position)
    this.AddProgress('Disabled vprg44 ' k_Position)
    this.AddProgress('Disabled vprg45 ' k_Position)
    this.AddProgress('Disabled vprg46 ' k_Position)
    this.AddProgress('Disabled vprg47 ' k_Position)
    this.AddProgress('Disabled vprg48 ' k_Position)
    this.AddProgress('Disabled vprg49 ' k_Position)
    this.AddProgress('Disabled vprg50 ' k_Position)
    this.AddProgress('Disabled vprg51 ' k_Position)
    this.AddProgress('Disabled vprg52 ' k_Position)

    this.AddProgress('Disabled vprg53 x+' k_KeyMargin ' w' k_LastShiftWidth ' h' k_KeyHeight)
    this.AddProgress('Disabled ' k_Position)
    this.AddProgress('Disabled ' k_Position)
    this.AddProgress('Disabled vprg68 ' k_Position)
    this.AddProgress('Disabled ' k_Position)

    ; 5
    this.AddProgress('Disabled vprg54 xs y+' k_KeyMargin ' w' k_TabW ' h' k_KeyHeight)
    this.AddProgress('Disabled vprg55 x+' k_KeyMargin ' w' k_TabW ' h' k_KeyHeight)
    this.AddProgress('Disabled vprg56 x+' k_KeyMargin ' w' k_TabW ' h' k_KeyHeight)
    this.AddProgress('Disabled vprg57 x+' k_KeyMargin ' w' k_SpacebarWidth ' h' k_KeyHeight)
    this.AddProgress('Disabled vprg58 x+' k_KeyMargin ' w' k_TabW ' h' k_KeyHeight)
    this.AddProgress('Disabled vprg59 x+' k_KeyMargin ' w' k_TabW ' h' k_KeyHeight)
    this.AddProgress('Disabled vprg60 x+' k_KeyMargin ' w' k_TabW ' h' k_KeyHeight)

    this.AddProgress('Disabled vprg61 x+' k_KeyMargin ' w' k_LastCtrlWidth ' h' k_KeyHeight)
    this.AddProgress('Disabled ' k_Position)
    this.AddProgress('Disabled vprg69 ' k_Position)
    this.AddProgress('Disabled vprg70 ' k_Position)
    this.AddProgress('Disabled vprg71 ' k_Position)

    ; Overlay the progress bar with a button
    this.AddButton('Section xs ym ' k_KeySize, '``')
    this.AddButton(k_Position, '1')
    this.AddButton(k_Position, '2')
    this.AddButton(k_Position, '3')
    this.AddButton(k_Position, '4')
    this.AddButton(k_Position, '5')
    this.AddButton(k_Position, '6')
    this.AddButton(k_Position, '7')
    this.AddButton(k_Position, '8')
    this.AddButton(k_Position, '9')
    this.AddButton(k_Position, '0')
    this.AddButton(k_Position, '-')
    this.AddButton(k_Position, '=')
    this.AddButton(k_Position ' w' k_ShiftW ' h' k_KeyHeight, labels.Get('BackSpace'))
    this.AddProgress('Disabled Background' Config.guiProgressBackColor k_Position)
    this.AddButton(k_Position, 'Ins')
    this.AddButton(k_Position, labels.Get('Home'))
    this.AddButton(k_Position, labels.Get('PgUp'))

    ; 2
    this.AddButton('xs y+' k_KeyMargin ' w' k_TabW ' h' k_KeyHeight, labels.Get('Tab'))
    this.AddButton(k_Position, 'Q')
    this.AddButton(k_Position, 'W')
    this.AddButton(k_Position, 'E')
    this.AddButton(k_Position, 'R')
    this.AddButton(k_Position, 'T')
    this.AddButton(k_Position, 'Y')
    this.AddButton(k_Position, 'U')
    this.AddButton(k_Position, 'I')
    this.AddButton(k_Position, 'O')
    this.AddButton(k_Position, 'P')
    this.AddButton(k_Position, '[')
    this.AddButton(k_Position, ']')
    this.AddButton('x+' k_KeyMargin ' w' k_LastKeyWidth ' h' k_KeyHeight, '\')
    this.AddProgress('Disabled Background' Config.guiProgressBackColor k_Position)
    this.AddButton(k_Position, 'Del')
    this.AddButton(k_Position, labels.Get('End'))
    this.AddButton(k_Position, labels.Get('PgDn'))

    ; 3
    this.AddButton('xs y+' k_KeyMargin ' w' k_CapsW ' h' k_KeyHeight, labels.Get('CapsLock'))
    this.AddButton(k_Position, 'A')
    this.AddButton(k_Position, 'S')
    this.AddButton(k_Position, 'D')
    this.AddButton(k_Position, 'F')
    this.AddButton(k_Position, 'G')
    this.AddButton(k_Position, 'H')
    this.AddButton(k_Position, 'J')
    this.AddButton(k_Position, 'K')
    this.AddButton(k_Position, 'L')
    this.AddButton(k_Position, ';')
    this.AddButton(k_Position, '`'')
    this.AddButton('x+' k_KeyMargin ' w' k_EnterWidth ' h' k_KeyHeight, 'Enter')
    this.AddProgress('Disabled Background' Config.guiProgressBackColor k_Position)
    this.AddProgress('Disabled Background' Config.guiProgressBackColor k_Position)
    this.AddProgress('Disabled Background' Config.guiProgressBackColor k_Position)
    this.AddProgress('Disabled Background' Config.guiProgressBackColor k_Position)

    ; 4
    this.AddButton('xs y+' k_KeyMargin ' w' k_ShiftW ' h' k_KeyHeight, labels.Get('LShift'))
    this.AddButton(k_Position, 'Z')
    this.AddButton(k_Position, 'X')
    this.AddButton(k_Position, 'C')
    this.AddButton(k_Position, 'V')
    this.AddButton(k_Position, 'B')
    this.AddButton(k_Position, 'N')
    this.AddButton(k_Position, 'M')
    this.AddButton(k_Position, ',')
    this.AddButton(k_Position, '.')
    this.AddButton(k_Position, '/')
    this.AddButton('x+' k_KeyMargin ' w' k_LastShiftWidth ' h' k_KeyHeight, labels.Get('RShift'))
    this.AddProgress('Disabled Background' Config.guiProgressBackColor k_Position)
    this.AddProgress('Disabled Background' Config.guiProgressBackColor k_Position)
    this.AddButton(k_Position, labels.Get('Up'))
    this.AddProgress('Disabled Background' Config.guiProgressBackColor k_Position)

    ; 5
    this.AddButton('xs y+' k_KeyMargin ' w' k_TabW ' h' k_KeyHeight, labels.Get('LCtrl'))
    this.AddButton('x+' k_KeyMargin ' w' k_TabW ' h' k_KeyHeight, labels.Get('LWin'))
    this.AddButton('x+' k_KeyMargin ' w' k_TabW ' h' k_KeyHeight, labels.Get('LAlt'))
    this.AddButton('x+' k_KeyMargin ' w' k_SpacebarWidth ' h' k_KeyHeight, 'Space')
    this.AddButton('x+' k_KeyMargin ' w' k_TabW ' h' k_KeyHeight, labels.Get('RAlt'))
    this.AddButton('x+' k_KeyMargin ' w' k_TabW ' h' k_KeyHeight, labels.Get('RWin'))
    this.AddButton('x+' k_KeyMargin ' w' k_TabW ' h' k_KeyHeight, labels.Get('AppsKey'))
    this.AddButton('x+' k_KeyMargin ' w' k_LastCtrlWidth ' h' k_KeyHeight, labels.Get('RCtrl'))
    this.AddProgress('Disabled Background' Config.guiProgressBackColor k_Position)
    this.AddButton(k_Position, labels.Get('Left'))
    this.AddButton(k_Position, labels.Get('Down'))
    this.AddButton(k_Position, labels.Get('Right'))
  }

  ; param:
  ; ret:   gui instance-->gui object
  ; desc:  display the gui and returns the current instance
  static Show() {
    ; singleton
    for w in WinGetList('ahk_pid ' ProcessExist())
      if (g := GuiFromHwnd(w)) && g.base = this.Prototype {
        g.Show()
        return g
      }
    ins := VK()
    VK.insHwnd := ins.Hwnd
    ins.Show('y' A_ScreenHeight * (3 / 4))
    if Config.guiCapacity
      WinSetTransparent(Config.guiCapacity, 'ahk_id' ins.Hwnd)
    return ins
  }

  ; param:
  ; ret:   -->
  ; desc:  distory
  static Cancel() {
    WinClose('ahk_id' VK.insHwnd)
  }
}

; display keyboard
vkv := VK.Show()

; class-desc: some methods
class Utils {

  ; param:
  ; ret:   -->boolean
  ; desc:
  static IsOverGui() {
    WinGetPos(&guiX, &guiY, &guiW, &guiH, 'ahk_id' VK.insHwnd)
    MouseGetPos(&x, &y)
    if x < guiX || x > guiX + guiW
      return false
    if y < guiY || y > guiY + guiH
      return false
    return true
  }

  ; param:
  ; ret:   -->
  ; desc:  restore the position and transparent
  static RestoreVK() {
    WinMove(, A_ScreenHeight * (3 / 4), , , 'ahk_id' VK.insHwnd)
    Config.guiCapacity := 255
    WinSetTransparent(Config.guiCapacity, 'ahk_id' VK.insHwnd)
  }

  ; static KeyWaitAny() {
  ;   ih := InputHook()
  ;   ih.KeyOpt("{All}", "E")
  ;   ih.Start()
  ;   ih.Wait()
  ;   return ih.EndKey
  ; }
}

; OneKey() {
; ; This method can only trigger one button at the same time, so I abandoned it
; ; but there are some redeeming features, No need to set hotkeys repeatedly
;   while true {
;     k := Utils.KeyWaitAny()
;     if k ~= 'i)\w*'
;       k := StrUpper(k)
;     try {
;       keySuffix := k_Characters.Get(k)
;       guiCtrl := GuiCtrlFromHwnd(vkv['prg' keySuffix].Hwnd)
;       guiCtrl.Opt('+Background' Config.guiProgressColor)
;       KeyWait(k)
;       guiCtrl.Opt('+Background' Config.guiProgressBackColor)
;       guiCtrl.Redraw()
;     }
;     catch as e
;       MsgBox e.Message
;   }
; }

; set all the keys as hotkey
; from ascii 45 to 96 but skip some keys
Loop 49 {
  k_char := Chr(A_Index + 44)
  if k_char ~= '[<>^``]'
    continue
  ; binding method
  Hotkey '~*' k_char, Flash
}

~*`::
~*Backspace::
~*Tab::
~*CapsLock::
~*'::
~*Enter::
~*LShift::
~*,::
~*RShift::
~*LCtrl::
~*LWin::
~*LAlt::
~*Space::
~*RAlt::
~*RWin::
~*AppsKey::
~*RCtrl::
~*Insert::
~*Home::
~*PgUp::
~*Delete::
~*End::
~*PgDn::
~*Up::
~*Left::
~*Down::
~*Right:: Flash()

; param:
; ret:   -->
; desc:  change color when holding button, redraw when releasing
Flash(*) {
  k_ThisHotKey := StrReplace(A_ThisHotkey, '~*', '')
  k_ThisHotKey := StrUpper(k_ThisHotKey)
  try {
    keySuffix := k_Characters.Get(K_ThisHotkey)
    guiCtrl := GuiCtrlFromHwnd(vkv['prg' keySuffix].Hwnd)
    guiCtrl.Opt('+Background' Config.guiProgressColor)
    KeyWait(K_ThisHotkey)
    guiCtrl.Opt('+Background' Config.guiProgressBackColor)
    guiCtrl.Redraw()
  }
  catch as e
    MsgBox e.Message
}

; move gui
Lshift & LButton::
{
  if !Utils.IsOverGui()
    return
  if (A_ThisHotkey = A_PriorHotkey && A_TimeSincePriorHotkey < 300) {
    Utils.RestoreVK()
    return
  }
  ; move gui window
  MouseGetPos(&px, &py)
  WinGetPos(&wx, &wy, , , 'ahk_id' VK.insHwnd)
  dx := wx - px, dy := wy - py
  SetWinDelay -1
  While GetKeyState("LButton", "P")
  {
    MouseGetPos(&nx, &ny)
    WinMove(nx + dx, ny + dy, , , 'ahk_id' VK.insHwnd)
  }
}

#HotIf !Config.EnableInput
; change the transparent via mouse whell
LShift & WheelUp::
{
  Config.guiCapacity += 10
  If Config.guiCapacity > 255
    Config.guiCapacity := 255
  WinSetTransparent(Config.guiCapacity, 'ahk_id' VK.insHwnd)
}

LShift & WheelDown::
{
  Config.guiCapacity -= 10
  If Config.guiCapacity < 0
    Config.guiCapacity := 10
  WinSetTransparent(Config.guiCapacity, 'ahk_id' VK.insHwnd)
}
#HotIf

#HotIf Config.EnableInput
; Outputs the text of the focus control
; But it's useless here
~LButton::
{
  if !Utils.IsOverGui()
    return
  KeyWait('LButton')
  ActiveBtnHwnd := ControlGetFocus('ahk_id' VK.insHwnd)
  MsgBox ControlGetText(ActiveBtnHwnd)
}
#HotIf